/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to you under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.calcite.sql.type;

import java.math.BigDecimal;
import java.sql.Types;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.alibaba.polardbx.common.utils.time.MySQLTimeTypeUtil;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.alibaba.polardbx.common.charset.CharsetName;
import io.airlift.slice.Slice;
import org.apache.calcite.avatica.util.TimeUnit;
import org.apache.calcite.sql.SqlLiteral;
import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.calcite.util.DateString;
import org.apache.calcite.util.TimeString;
import org.apache.calcite.util.TimestampString;
import org.apache.calcite.util.Util;
import org.openjdk.jol.info.ClassLayout;

/**
 * Enumeration of the type names which can be used to construct a SQL type.
 * Rationale for this class's existence (instead of just using the standard
 * java.sql.Type ordinals):
 * <ul>
 * <li>{@link java.sql.Types} does not include all SQL2003 data-types;
 * <li>SqlTypeName provides a type-safe enumeration;
 * <li>SqlTypeName provides a place to hang extra information such as whether
 * the type carries precision and scale.
 * </ul>
 *
 * @author chuanqin
 */
public enum SqlTypeName {
    BOOLEAN(PrecScale.NO_NO, false, Types.BOOLEAN, SqlTypeFamily.BOOLEAN),
    TINYINT(PrecScale.NO_NO | PrecScale.YES_NO, false, Types.TINYINT, SqlTypeFamily.NUMERIC),
    TINYINT_UNSIGNED(PrecScale.NO_NO | PrecScale.YES_NO, false, Types.TINYINT, SqlTypeFamily.NUMERIC),
    SMALLINT_UNSIGNED(PrecScale.NO_NO, false, Types.SMALLINT, SqlTypeFamily.NUMERIC),
    SMALLINT(PrecScale.NO_NO, false, Types.SMALLINT, SqlTypeFamily.NUMERIC),
    MEDIUMINT(PrecScale.NO_NO, false, Types.INTEGER, SqlTypeFamily.NUMERIC),
    MEDIUMINT_UNSIGNED(PrecScale.NO_NO, false, Types.INTEGER, SqlTypeFamily.NUMERIC),
    INTEGER(PrecScale.NO_NO, false, Types.INTEGER, SqlTypeFamily.NUMERIC),
    INTEGER_UNSIGNED(PrecScale.NO_NO, false, Types.INTEGER, SqlTypeFamily.NUMERIC),
    BIGINT(PrecScale.NO_NO, false, Types.BIGINT, SqlTypeFamily.NUMERIC),
    BIGINT_UNSIGNED(PrecScale.NO_NO, false, Types.BIGINT, SqlTypeFamily.NUMERIC),
    YEAR(PrecScale.NO_NO, false, Types.INTEGER, SqlTypeFamily.NUMERIC),
    DECIMAL(PrecScale.NO_NO | PrecScale.YES_NO | PrecScale.YES_YES, false, Types.DECIMAL,
        SqlTypeFamily.NUMERIC),
    FLOAT(PrecScale.NO_NO, false, Types.FLOAT, SqlTypeFamily.NUMERIC),
    REAL(PrecScale.NO_NO, false, Types.REAL, SqlTypeFamily.NUMERIC),
    DOUBLE(PrecScale.NO_NO, false, Types.DOUBLE, SqlTypeFamily.NUMERIC),

    // TODO: remove UNSIGNED/SIGNED
    // add by xiaoying
    UNSIGNED(PrecScale.NO_NO, false, Types.BIGINT, SqlTypeFamily.NUMERIC),
    SIGNED(PrecScale.NO_NO, false, Types.BIGINT, SqlTypeFamily.NUMERIC),

    DATETIME(PrecScale.NO_NO | PrecScale.YES_NO | PrecScale.YES_YES, false, Types.TIMESTAMP, SqlTypeFamily.DATETIME),

    ENUM(PrecScale.NO_NO, false, Types.ARRAY, SqlTypeFamily.ARRAY),

    DATE(PrecScale.NO_NO, false, Types.DATE, SqlTypeFamily.DATE),
    TIME(PrecScale.NO_NO | PrecScale.YES_NO | PrecScale.YES_YES, false, Types.TIME, SqlTypeFamily.TIME),
    TIME_WITH_LOCAL_TIME_ZONE(PrecScale.NO_NO | PrecScale.YES_NO | PrecScale.YES_YES, false, Types.OTHER,
        SqlTypeFamily.TIME),
    TIMESTAMP(PrecScale.NO_NO | PrecScale.YES_NO | PrecScale.YES_YES, false, Types.TIMESTAMP, SqlTypeFamily.TIMESTAMP),
    TIMESTAMP_WITH_LOCAL_TIME_ZONE(PrecScale.NO_NO | PrecScale.YES_NO | PrecScale.YES_YES, false, Types.OTHER,
        SqlTypeFamily.TIMESTAMP),
    INTERVAL_YEAR(PrecScale.NO_NO, false, Types.OTHER, SqlTypeFamily.INTERVAL_YEAR_MONTH),
    INTERVAL_YEAR_MONTH(PrecScale.NO_NO, false, Types.OTHER, SqlTypeFamily.INTERVAL_YEAR_MONTH),
    INTERVAL_MONTH(PrecScale.NO_NO, false, Types.OTHER, SqlTypeFamily.INTERVAL_YEAR_MONTH),
    INTERVAL_DAY(PrecScale.NO_NO | PrecScale.YES_NO | PrecScale.YES_YES, false, Types.OTHER,
        SqlTypeFamily.INTERVAL_DAY_TIME),
    INTERVAL_DAY_HOUR(PrecScale.NO_NO | PrecScale.YES_NO | PrecScale.YES_YES, false, Types.OTHER,
        SqlTypeFamily.INTERVAL_DAY_TIME),
    INTERVAL_DAY_MINUTE(PrecScale.NO_NO | PrecScale.YES_NO | PrecScale.YES_YES, false, Types.OTHER,
        SqlTypeFamily.INTERVAL_DAY_TIME),
    INTERVAL_DAY_SECOND(PrecScale.NO_NO | PrecScale.YES_NO | PrecScale.YES_YES, false, Types.OTHER,
        SqlTypeFamily.INTERVAL_DAY_TIME),
    INTERVAL_DAY_MICROSECOND(PrecScale.NO_NO | PrecScale.YES_NO | PrecScale.YES_YES, false,
        Types.OTHER, SqlTypeFamily.INTERVAL_DAY_TIME),
    INTERVAL_HOUR(PrecScale.NO_NO | PrecScale.YES_NO | PrecScale.YES_YES, false, Types.OTHER,
        SqlTypeFamily.INTERVAL_DAY_TIME),
    INTERVAL_HOUR_MINUTE(PrecScale.NO_NO | PrecScale.YES_NO | PrecScale.YES_YES, false,
        Types.OTHER, SqlTypeFamily.INTERVAL_DAY_TIME),
    INTERVAL_HOUR_MICROSECOND(PrecScale.NO_NO | PrecScale.YES_NO | PrecScale.YES_YES, false,
        Types.OTHER, SqlTypeFamily.INTERVAL_DAY_TIME),
    INTERVAL_HOUR_SECOND(PrecScale.NO_NO | PrecScale.YES_NO | PrecScale.YES_YES, false,
        Types.OTHER, SqlTypeFamily.INTERVAL_DAY_TIME),
    INTERVAL_MINUTE(PrecScale.NO_NO | PrecScale.YES_NO | PrecScale.YES_YES, false, Types.OTHER,
        SqlTypeFamily.INTERVAL_DAY_TIME),
    INTERVAL_MINUTE_SECOND(PrecScale.NO_NO | PrecScale.YES_NO | PrecScale.YES_YES, false,
        Types.OTHER, SqlTypeFamily.INTERVAL_DAY_TIME),
    INTERVAL_MINUTE_MICROSECOND(PrecScale.NO_NO | PrecScale.YES_NO | PrecScale.YES_YES, false,
        Types.OTHER, SqlTypeFamily.INTERVAL_DAY_TIME),
    INTERVAL_SECOND(PrecScale.NO_NO | PrecScale.YES_NO | PrecScale.YES_YES, false, Types.OTHER,
        SqlTypeFamily.INTERVAL_DAY_TIME),
    INTERVAL(PrecScale.NO_NO | PrecScale.YES_NO | PrecScale.YES_YES, false, Types.OTHER,
        SqlTypeFamily.TIMESTAMP),
    INTERVAL_SECOND_MICROSECOND(PrecScale.NO_NO | PrecScale.YES_NO | PrecScale.YES_YES, false, Types.OTHER,
        SqlTypeFamily.INTERVAL_DAY_TIME),
    INTERVAL_MICROSECOND(PrecScale.NO_NO | PrecScale.YES_NO | PrecScale.YES_YES, false, Types.OTHER,
        SqlTypeFamily.INTERVAL_DAY_TIME),
    CHAR(PrecScale.NO_NO | PrecScale.YES_NO, false, Types.CHAR, SqlTypeFamily.CHARACTER),
    VARCHAR(PrecScale.NO_NO | PrecScale.YES_NO, false, Types.VARCHAR, SqlTypeFamily.CHARACTER),
    BINARY_VARCHAR(PrecScale.NO_NO | PrecScale.YES_NO, false, Types.VARCHAR, SqlTypeFamily.CHARACTER),
    BLOB(PrecScale.NO_NO, false, Types.BLOB, SqlTypeFamily.BINARY),

    BINARY(PrecScale.NO_NO | PrecScale.YES_NO, false, Types.BINARY, SqlTypeFamily.BINARY),
    BIT(PrecScale.NO_NO | PrecScale.YES_NO, false, Types.BINARY, SqlTypeFamily.BINARY),
    BIG_BIT(PrecScale.NO_NO | PrecScale.YES_NO, false, Types.BINARY, SqlTypeFamily.BINARY),

    VARBINARY(PrecScale.NO_NO | PrecScale.YES_NO, false, Types.VARBINARY, SqlTypeFamily.BINARY),
    NULL(PrecScale.NO_NO, true, Types.NULL, SqlTypeFamily.NULL),
    ANY(PrecScale.NO_NO | PrecScale.YES_NO | PrecScale.YES_YES, true, Types.JAVA_OBJECT,
        SqlTypeFamily.ANY),
    SYMBOL(PrecScale.NO_NO, true, Types.OTHER, null),
    MULTISET(PrecScale.NO_NO, false, Types.ARRAY, SqlTypeFamily.MULTISET),
    ARRAY(PrecScale.NO_NO, false, Types.ARRAY, SqlTypeFamily.ARRAY),
    MAP(PrecScale.NO_NO, false, Types.OTHER, SqlTypeFamily.MAP),
    DISTINCT(PrecScale.NO_NO, false, Types.DISTINCT, null),
    STRUCTURED(PrecScale.NO_NO, false, Types.STRUCT, null),
    ROW(PrecScale.NO_NO, false, Types.STRUCT, null),
    OTHER(PrecScale.NO_NO, false, Types.OTHER, null),
    CURSOR(PrecScale.NO_NO, false, ExtraSqlTypes.REF_CURSOR, SqlTypeFamily.CURSOR),
    COLUMN_LIST(PrecScale.NO_NO, false, Types.OTHER + 2, SqlTypeFamily.COLUMN_LIST),
    DYNAMIC_STAR(PrecScale.NO_NO | PrecScale.YES_NO | PrecScale.YES_YES, true, Types.JAVA_OBJECT,
        SqlTypeFamily.ANY),
    JSON(PrecScale.NO_NO, false, Types.VARCHAR, SqlTypeFamily.CHARACTER),
    GEOMETRY(PrecScale.NO_NO, true, ExtraSqlTypes.GEOMETRY, SqlTypeFamily.GEO),
    USER_SYMBOL(PrecScale.NO_NO | PrecScale.YES_NO, false, Types.CHAR, SqlTypeFamily.CHARACTER);

    public static final int MAX_DATETIME_PRECISION = MySQLTimeTypeUtil.MAX_DATETIME_PRECISION;
    public static final int MAX_TIME_PRECISION = MySQLTimeTypeUtil.MAX_TIME_PRECISION;
    public static final int MAX_BIT_PRECISION = 63;
    public static final int MIN_BIT_PRECISION = 1;

    // Minimum and default interval precisions are defined by SQL2003
    // Maximum interval precisions are implementation dependent,
    // but must be at least the default value
    public static final int DEFAULT_INTERVAL_START_PRECISION = 2;
    public static final int DEFAULT_INTERVAL_FRACTIONAL_SECOND_PRECISION = 6;
    public static final int MIN_INTERVAL_START_PRECISION = 1;
    public static final int MIN_INTERVAL_FRACTIONAL_SECOND_PRECISION = 1;
    public static final int MAX_INTERVAL_START_PRECISION = 10;
    public static final int MAX_INTERVAL_FRACTIONAL_SECOND_PRECISION = 9;
    public static final int MAX_TIME_FRACTIONAL_SECOND_SCALE = 6;

    // Cached map of enum values
    private static final Map<String, SqlTypeName> VALUES_MAP = Util.enumConstants(SqlTypeName.class);

    // categorizations used by SqlTypeFamily definitions

    // you probably want to use JDK 1.5 support for treating enumeration
    // as collection instead; this is only here to support
    // SqlTypeFamily.ANY
    public static final List<SqlTypeName> ALL_TYPES = ImmutableList.of(BOOLEAN,
        INTEGER,
        INTEGER_UNSIGNED,
        VARCHAR,
        DATE,
        DATETIME,
        TIME,
        TIMESTAMP,
        NULL,
        DECIMAL,
        ANY,
        CHAR,
        BINARY,
        VARBINARY,
        TINYINT,
        TINYINT_UNSIGNED,
        SMALLINT,
        SMALLINT_UNSIGNED,
        MEDIUMINT,
        MEDIUMINT_UNSIGNED,
        BIGINT,
        BIGINT_UNSIGNED,
        REAL,
        DOUBLE,
        SYMBOL,
        INTERVAL_YEAR,
        INTERVAL_YEAR_MONTH,
        INTERVAL_MONTH,
        INTERVAL_DAY,
        INTERVAL_DAY_HOUR,
        INTERVAL_DAY_MINUTE,
        INTERVAL_DAY_SECOND,
        INTERVAL_HOUR,
        INTERVAL_HOUR_MINUTE,
        INTERVAL_HOUR_SECOND,
        INTERVAL_MINUTE,
        INTERVAL_MINUTE_SECOND,
        INTERVAL_SECOND,
        TIME_WITH_LOCAL_TIME_ZONE,
        TIMESTAMP_WITH_LOCAL_TIME_ZONE,
        FLOAT,
        MULTISET,
        DISTINCT,
        STRUCTURED,
        ROW,
        CURSOR,
        COLUMN_LIST,
        BIG_BIT);

    public static final List<SqlTypeName> BOOLEAN_TYPES = ImmutableList.of(BOOLEAN);

    public static final List<SqlTypeName> BINARY_TYPES = ImmutableList.of(BINARY,
        VARBINARY);

    public static final List<SqlTypeName> INT_TYPES = ImmutableList.of(
        TINYINT,
        TINYINT_UNSIGNED,
        SMALLINT,
        SMALLINT_UNSIGNED,
        MEDIUMINT,
        MEDIUMINT_UNSIGNED,
        INTEGER,
        INTEGER_UNSIGNED,
        SIGNED,
        UNSIGNED,
        BIGINT,
        BIGINT_UNSIGNED);

    public static final List<SqlTypeName> EXACT_TYPES = combine(INT_TYPES,
        ImmutableList.of(DECIMAL));

    public static final List<SqlTypeName> APPROX_TYPES = ImmutableList.of(FLOAT,
        REAL,
        DOUBLE);

    public static final List<SqlTypeName> NUMERIC_TYPES = combine(EXACT_TYPES,
        APPROX_TYPES);

    public static final List<SqlTypeName> FRACTIONAL_TYPES = combine(APPROX_TYPES,
        ImmutableList.of(DECIMAL));

    public static final List<SqlTypeName> BIGINT_UNSIGNED_TYPES = ImmutableList.of(BIGINT_UNSIGNED,
        UNSIGNED);

    public static final List<SqlTypeName> CHAR_TYPES = ImmutableList.of(CHAR,
        VARCHAR);

    public static final List<SqlTypeName> STRING_TYPES = combine(CHAR_TYPES,
        BINARY_TYPES);

    public static final List<SqlTypeName> DATETIME_TYPES = ImmutableList.of(DATE,
        TIME,
        TIME_WITH_LOCAL_TIME_ZONE,
        TIMESTAMP,
        TIMESTAMP_WITH_LOCAL_TIME_ZONE,
        DATETIME);
    public static final List<SqlTypeName> DATETIME_YEAR_TYPES = ImmutableList.of(DATE,
        TIME,
        TIME_WITH_LOCAL_TIME_ZONE,
        TIMESTAMP,
        TIMESTAMP_WITH_LOCAL_TIME_ZONE,
        DATETIME,
        YEAR);

    public static final Set<SqlTypeName> YEAR_INTERVAL_TYPES = Sets.immutableEnumSet(SqlTypeName.INTERVAL_YEAR,
        SqlTypeName.INTERVAL_YEAR_MONTH,
        SqlTypeName.INTERVAL_MONTH);

    public static final Set<SqlTypeName> DAY_INTERVAL_TYPES = Sets.immutableEnumSet(SqlTypeName.INTERVAL_DAY,
        SqlTypeName.INTERVAL_DAY_HOUR,
        SqlTypeName.INTERVAL_DAY_MINUTE,
        SqlTypeName.INTERVAL_DAY_SECOND,
        SqlTypeName.INTERVAL_DAY_MICROSECOND,
        SqlTypeName.INTERVAL_HOUR,
        SqlTypeName.INTERVAL_HOUR_MINUTE,
        SqlTypeName.INTERVAL_HOUR_SECOND,
        SqlTypeName.INTERVAL_HOUR_MICROSECOND,
        SqlTypeName.INTERVAL_MINUTE,
        SqlTypeName.INTERVAL_MINUTE_SECOND,
        SqlTypeName.INTERVAL_MINUTE_MICROSECOND,
        SqlTypeName.INTERVAL_SECOND,
        SqlTypeName.INTERVAL_SECOND_MICROSECOND,
        SqlTypeName.INTERVAL_MICROSECOND);
    private static final Map<Integer, SqlTypeName> UNSIGNED_JDBC_TYPE_TO_NAME =
        ImmutableMap.<Integer, SqlTypeName>builder()
            .put(Types.TINYINT, TINYINT_UNSIGNED)
            .put(Types.SMALLINT, SMALLINT_UNSIGNED)
            .put(Types.BIGINT, BIGINT_UNSIGNED)
            .put(Types.INTEGER, INTEGER_UNSIGNED)
            .build();
    public static final Set<SqlTypeName> INTERVAL_TYPES = Sets.immutableEnumSet(Iterables.concat(
        YEAR_INTERVAL_TYPES,
        DAY_INTERVAL_TYPES));

    private static final Map<Integer, SqlTypeName> JDBC_TYPE_TO_NAME = ImmutableMap.<Integer, SqlTypeName>builder()
        .put(Types.TINYINT, TINYINT)
        .put(Types.SMALLINT, SMALLINT)
        .put(Types.BIGINT, BIGINT)
        .put(Types.INTEGER, INTEGER)
        .put(Types.NUMERIC, DECIMAL)
        .put(Types.DECIMAL, DECIMAL)
        .put(Types.FLOAT, FLOAT)
        .put(Types.REAL, FLOAT)
        .put(Types.DOUBLE, DOUBLE)
        .put(Types.CHAR, CHAR)
        .put(Types.VARCHAR, VARCHAR)
        .put(ExtraSqlTypes.NCHAR, CHAR)
        .put(ExtraSqlTypes.NVARCHAR, VARCHAR)
        .put(Types.LONGVARCHAR, VARCHAR)
        .put(Types.CLOB, VARCHAR)
        .put(Types.BLOB, BLOB)
        .put(Types.BINARY, BINARY)
        .put(Types.VARBINARY, VARBINARY)
        .put(Types.LONGVARBINARY, VARBINARY)
        .put(Types.DATE, DATE)
        .put(Types.TIME, TIME)
        .put(Types.TIMESTAMP, TIMESTAMP)
        .put(Types.BIT, BIT)
        .put(Types.BOOLEAN, BOOLEAN)
        .put(Types.DISTINCT, DISTINCT)
        .put(Types.STRUCT, STRUCTURED)
        .put(Types.ARRAY, ARRAY)
        .put(Types.NULL, NULL)
        .build();

    public static final Map<SqlTypeName, SqlTypeName> SIGNED_TO_UNSIGNED = ImmutableMap.<SqlTypeName, SqlTypeName>builder()
        .put(TINYINT, TINYINT_UNSIGNED)
        .put(SMALLINT, SMALLINT_UNSIGNED)
        .put(MEDIUMINT, MEDIUMINT_UNSIGNED)
        .put(INTEGER, INTEGER_UNSIGNED)
        .put(BIGINT, BIGINT_UNSIGNED)
        .put(SIGNED, BIGINT_UNSIGNED)
        .build();

    public static final Map<SqlTypeName, SqlTypeName> UNSIGNED_TO_SIGNED = ImmutableMap.<SqlTypeName, SqlTypeName>builder()
        .put(TINYINT_UNSIGNED, TINYINT)
        .put(SMALLINT_UNSIGNED, SMALLINT)
        .put(MEDIUMINT_UNSIGNED, MEDIUMINT)
        .put(INTEGER_UNSIGNED, INTEGER)
        .put(BIGINT_UNSIGNED, BIGINT)
        .put(UNSIGNED, BIGINT)
        .build();
    private static final long INSTANCE_SIZE_WITH_OBJECT = ClassLayout.parseClass(SqlTypeName.class).instanceSize()
        + SqlTypeFamily.INSTANCE_SIZE;

    public static final List<SqlTypeName> SIGN_TYPE = ImmutableList.of(SIGNED, UNSIGNED);

    /**
     * Bitwise-or of flags indicating allowable precision/scale combinations.
     */
    private final int signatures;

    /**
     * Returns true if not of a "pure" standard sql type. "Inpure" types are
     * {@link #ANY}, {@link #NULL} and {@link #SYMBOL}
     */
    private final boolean special;
    private final int jdbcOrdinal;
    private final SqlTypeFamily family;

    private static final SqlTypeName[] CACHED_VALUES = values();

    SqlTypeName(int signatures, boolean special, int jdbcType, SqlTypeFamily family) {
        this.signatures = signatures;
        this.special = special;
        this.jdbcOrdinal = jdbcType;
        this.family = family;
    }

    public static long getSize() {
        // ignore the internal size of SqlTypeFamily field
        return INSTANCE_SIZE_WITH_OBJECT;
    }

    /**
     * Looks up a type name from its name.
     *
     * @return Type name, or null if not found
     */
    public static SqlTypeName get(String name) {
        if (false) {
            // The following code works OK, but the spurious exceptions are
            // annoying.
            try {
                return SqlTypeName.valueOf(name);
            } catch (IllegalArgumentException e) {
                return null;
            }
        }
        return VALUES_MAP.get(name);
    }

    public boolean allowsNoPrecNoScale() {
        return (signatures & PrecScale.NO_NO) != 0;
    }

    public boolean allowsPrecNoScale() {
        return (signatures & PrecScale.YES_NO) != 0;
    }

    public boolean allowsPrec() {
        return allowsPrecScale(true, true) || allowsPrecScale(true, false);
    }

    public boolean allowsScale() {
        return allowsPrecScale(true, true);
    }

    public static SqlTypeName getValue(int ordinal) {
        return ordinal < 0 || ordinal >= CACHED_VALUES.length
            ? null
            : CACHED_VALUES[ordinal];
    }

    /**
     * Returns whether this type can be specified with a given combination of
     * precision and scale. For example,
     * <ul>
     * <li><code>Varchar.allowsPrecScale(true, false)</code> returns <code>
     * true</code>, because the VARCHAR type allows a precision parameter, as in
     * <code>VARCHAR(10)</code>.</li>
     * <li><code>Varchar.allowsPrecScale(true, true)</code> returns <code>
     * true</code>, because the VARCHAR type does not allow a precision and a
     * scale parameter, as in <code>VARCHAR(10, 4)</code>.</li>
     * <li><code>allowsPrecScale(false, true)</code> returns <code>false</code>
     * for every type.</li>
     * </ul>
     *
     * @param precision Whether the precision/length field is part of the type
     * specification
     * @param scale Whether the scale field is part of the type specification
     * @return Whether this combination of precision/scale is valid
     */
    public boolean allowsPrecScale(boolean precision, boolean scale) {
        int mask = precision ? (scale ? PrecScale.YES_YES : PrecScale.YES_NO) : (scale ? 0 : PrecScale.NO_NO);
        return (signatures & mask) != 0;
    }

    public boolean isSpecial() {
        return special;
    }

    /**
     * @return the ordinal from {@link java.sql.Types} corresponding to this
     * SqlTypeName
     */
    public int getJdbcOrdinal() {
        return jdbcOrdinal;
    }

    private static List<SqlTypeName> combine(List<SqlTypeName> list0, List<SqlTypeName> list1) {
        return ImmutableList.<SqlTypeName>builder().addAll(list0).addAll(list1).build();
    }

    /**
     * @return default scale for this type if supported, otherwise -1 if scale
     * is either unsupported or must be specified explicitly
     */
    public int getDefaultScale() {
        switch (this) {
        case DECIMAL:
            return 0;
        case INTERVAL_YEAR:
        case INTERVAL_YEAR_MONTH:
        case INTERVAL_MONTH:
        case INTERVAL_DAY:
        case INTERVAL_DAY_HOUR:
        case INTERVAL_DAY_MINUTE:
        case INTERVAL_DAY_SECOND:
        case INTERVAL_HOUR:
        case INTERVAL_HOUR_MINUTE:
        case INTERVAL_HOUR_SECOND:
        case INTERVAL_MINUTE:
        case INTERVAL_MINUTE_SECOND:
        case INTERVAL_SECOND:
            return DEFAULT_INTERVAL_FRACTIONAL_SECOND_PRECISION;
        default:
            return -1;
        }
    }

    /**
     * Gets the SqlTypeFamily containing this SqlTypeName.
     *
     * @return containing family, or null for none
     */
    public SqlTypeFamily getFamily() {
        return family;
    }

    /**
     * Gets the SqlTypeName corresponding to a JDBC type.
     *
     * @param jdbcType the JDBC type of interest
     * @return corresponding SqlTypeName, or null if the type is not known
     */
    public static SqlTypeName getNameForJdbcType(int jdbcType) {
        return JDBC_TYPE_TO_NAME.get(jdbcType);
    }

    public static SqlTypeName getNameForUnsignedJdbcType(int jdbcType) {
        return UNSIGNED_JDBC_TYPE_TO_NAME.get(jdbcType);
    }

    /**
     * Returns the limit of this datatype. For example,
     * <table border="1">
     * <caption>Datatype limits</caption>
     * <tr>
     * <th>Datatype</th>
     * <th>sign</th>
     * <th>limit</th>
     * <th>beyond</th>
     * <th>precision</th>
     * <th>scale</th>
     * <th>Returns</th>
     * </tr>
     * <tr>
     * <td>Integer</td>
     * <td>true</td>
     * <td>true</td>
     * <td>false</td>
     * <td>-1</td>
     * <td>-1</td>
     * <td>2147483647 (2 ^ 31 -1 = MAXINT)</td>
     * </tr>
     * <tr>
     * <td>Integer</td>
     * <td>true</td>
     * <td>true</td>
     * <td>true</td>
     * <td>-1</td>
     * <td>-1</td>
     * <td>2147483648 (2 ^ 31 = MAXINT + 1)</td>
     * </tr>
     * <tr>
     * <td>Integer</td>
     * <td>false</td>
     * <td>true</td>
     * <td>false</td>
     * <td>-1</td>
     * <td>-1</td>
     * <td>-2147483648 (-2 ^ 31 = MININT)</td>
     * </tr>
     * <tr>
     * <td>Boolean</td>
     * <td>true</td>
     * <td>true</td>
     * <td>false</td>
     * <td>-1</td>
     * <td>-1</td>
     * <td>TRUE</td>
     * </tr>
     * <tr>
     * <td>Varchar</td>
     * <td>true</td>
     * <td>true</td>
     * <td>false</td>
     * <td>10</td>
     * <td>-1</td>
     * <td>'ZZZZZZZZZZ'</td>
     * </tr>
     * </table>
     *
     * @param sign If true, returns upper limit, otherwise lower limit
     * @param limit If true, returns value at or near to overflow; otherwise
     * value at or near to underflow
     * @param beyond If true, returns the value just beyond the limit, otherwise
     * the value at the limit
     * @param precision Precision, or -1 if not applicable
     * @param scale Scale, or -1 if not applicable
     * @return Limit value
     */
    public Object getLimit(boolean sign, Limit limit, boolean beyond, int precision, int scale) {
        assert allowsPrecScale(precision != -1, scale != -1) : this;
        if (limit == Limit.ZERO) {
            if (beyond) {
                return null;
            }
            sign = true;
        }
        Calendar calendar;

        switch (this) {
        case BOOLEAN:
            switch (limit) {
            case ZERO:
                return false;
            case UNDERFLOW:
                return null;
            case OVERFLOW:
                if (beyond || !sign) {
                    return null;
                } else {
                    return true;
                }
            default:
                throw Util.unexpected(limit);
            }

        case TINYINT:
            return getNumericLimit(2, 8, sign, limit, beyond);

        case SMALLINT:
            return getNumericLimit(2, 16, sign, limit, beyond);

        case INTEGER:
            return getNumericLimit(2, 32, sign, limit, beyond);

        case BIGINT:
            return getNumericLimit(2, 64, sign, limit, beyond);

        case DECIMAL:
            BigDecimal decimal = getNumericLimit(10, precision, sign, limit, beyond);
            if (decimal == null) {
                return null;
            }

            // Decimal values must fit into 64 bits. So, the maximum value
            // of
            // a DECIMAL(19, 0) is 2^63 - 1, not 10^19 - 1.
            switch (limit) {
            case OVERFLOW:
                final BigDecimal other = (BigDecimal) BIGINT.getLimit(sign, limit, beyond, -1, -1);
                if (decimal.compareTo(other) == (sign ? 1 : -1)) {
                    decimal = other;
                }
            }

            // Apply scale.
            if (scale == 0) {
                // do nothing
            } else if (scale > 0) {
                decimal = decimal.divide(BigDecimal.TEN.pow(scale));
            } else {
                decimal = decimal.multiply(BigDecimal.TEN.pow(-scale));
            }
            return decimal;

        case CHAR:
        case VARCHAR:
            if (!sign) {
                return null; // this type does not have negative values
            }
            StringBuilder buf = new StringBuilder();
            switch (limit) {
            case ZERO:
                break;
            case UNDERFLOW:
                if (beyond) {
                    // There is no value between the empty string and
                    // the
                    // smallest non-empty string.
                    return null;
                }
                buf.append("a");
                break;
            case OVERFLOW:
                for (int i = 0; i < precision; ++i) {
                    buf.append("Z");
                }
                if (beyond) {
                    buf.append("Z");
                }
                break;
            }
            return buf.toString();

        case BINARY:
        case VARBINARY:
            if (!sign) {
                return null; // this type does not have negative values
            }
            byte[] bytes;
            switch (limit) {
            case ZERO:
                bytes = new byte[0];
                break;
            case UNDERFLOW:
                if (beyond) {
                    // There is no value between the empty string and
                    // the
                    // smallest value.
                    return null;
                }
                bytes = new byte[] {0x00};
                break;
            case OVERFLOW:
                bytes = new byte[precision + (beyond ? 1 : 0)];
                Arrays.fill(bytes, (byte) 0xff);
                break;
            default:
                throw Util.unexpected(limit);
            }
            return bytes;

        case DATE:
            calendar = Util.calendar();
            switch (limit) {
            case ZERO:

                // The epoch.
                calendar.set(Calendar.YEAR, 1970);
                calendar.set(Calendar.MONTH, 0);
                calendar.set(Calendar.DAY_OF_MONTH, 1);
                break;
            case UNDERFLOW:
                return null;
            case OVERFLOW:
                if (beyond) {
                    // It is impossible to represent an invalid year as
                    // a date
                    // literal. SQL dates are represented as
                    // 'yyyy-mm-dd', and
                    // 1 <= yyyy <= 9999 is valid. There is no year 0:
                    // the year
                    // before 1AD is 1BC, so SimpleDateFormat renders
                    // the day
                    // before 0001-01-01 (AD) as 0001-12-31 (BC), which
                    // looks
                    // like a valid date.
                    return null;
                }

                // "SQL:2003 6.1 <data type> Access Rules 6" says that
                // year is
                // between 1 and 9999, and days/months are the valid
                // Gregorian
                // calendar values for these years.
                if (sign) {
                    calendar.set(Calendar.YEAR, 9999);
                    calendar.set(Calendar.MONTH, 11);
                    calendar.set(Calendar.DAY_OF_MONTH, 31);
                } else {
                    calendar.set(Calendar.YEAR, 1);
                    calendar.set(Calendar.MONTH, 0);
                    calendar.set(Calendar.DAY_OF_MONTH, 1);
                }
                break;
            }
            calendar.set(Calendar.HOUR_OF_DAY, 0);
            calendar.set(Calendar.MINUTE, 0);
            calendar.set(Calendar.SECOND, 0);
            return calendar;

        case TIME:
            if (!sign) {
                return null;
                // this type does not have negative values
            }
            if (beyond) {
                return null;
                // invalid values are impossible to represent
            }
            calendar = Util.calendar();
            switch (limit) {
            case ZERO:

                // The epoch.
                calendar.set(Calendar.HOUR_OF_DAY, 0);
                calendar.set(Calendar.MINUTE, 0);
                calendar.set(Calendar.SECOND, 0);
                calendar.set(Calendar.MILLISECOND, 0);
                break;
            case UNDERFLOW:
                return null;
            case OVERFLOW:
                calendar.set(Calendar.HOUR_OF_DAY, 23);
                calendar.set(Calendar.MINUTE, 59);
                calendar.set(Calendar.SECOND, 59);
                int millis = (precision >= 3) ? 999 : ((precision == 2) ? 990 : ((precision == 1) ? 900 : 0));
                calendar.set(Calendar.MILLISECOND, millis);
                break;
            }
            return calendar;

        case TIMESTAMP:
            calendar = Util.calendar();
            switch (limit) {
            case ZERO:

                // The epoch.
                calendar.set(Calendar.YEAR, 1970);
                calendar.set(Calendar.MONTH, 0);
                calendar.set(Calendar.DAY_OF_MONTH, 1);
                calendar.set(Calendar.HOUR_OF_DAY, 0);
                calendar.set(Calendar.MINUTE, 0);
                calendar.set(Calendar.SECOND, 0);
                calendar.set(Calendar.MILLISECOND, 0);
                break;
            case UNDERFLOW:
                return null;
            case OVERFLOW:
                if (beyond) {
                    // It is impossible to represent an invalid year as
                    // a date
                    // literal. SQL dates are represented as
                    // 'yyyy-mm-dd', and
                    // 1 <= yyyy <= 9999 is valid. There is no year 0:
                    // the year
                    // before 1AD is 1BC, so SimpleDateFormat renders
                    // the day
                    // before 0001-01-01 (AD) as 0001-12-31 (BC), which
                    // looks
                    // like a valid date.
                    return null;
                }

                // "SQL:2003 6.1 <data type> Access Rules 6" says that
                // year is
                // between 1 and 9999, and days/months are the valid
                // Gregorian
                // calendar values for these years.
                if (sign) {
                    calendar.set(Calendar.YEAR, 9999);
                    calendar.set(Calendar.MONTH, 11);
                    calendar.set(Calendar.DAY_OF_MONTH, 31);
                    calendar.set(Calendar.HOUR_OF_DAY, 23);
                    calendar.set(Calendar.MINUTE, 59);
                    calendar.set(Calendar.SECOND, 59);
                    int millis = (precision >= 3) ? 999 : ((precision == 2) ? 990 : ((precision == 1) ? 900 : 0));
                    calendar.set(Calendar.MILLISECOND, millis);
                } else {
                    calendar.set(Calendar.YEAR, 1);
                    calendar.set(Calendar.MONTH, 0);
                    calendar.set(Calendar.DAY_OF_MONTH, 1);
                    calendar.set(Calendar.HOUR_OF_DAY, 0);
                    calendar.set(Calendar.MINUTE, 0);
                    calendar.set(Calendar.SECOND, 0);
                    calendar.set(Calendar.MILLISECOND, 0);
                }
                break;
            }
            return calendar;

        default:
            throw Util.unexpected(this);
        }
    }

    /**
     * Returns the minimum precision (or length) allowed for this type, or -1 if
     * precision/length are not applicable for this type.
     *
     * @return Minimum allowed precision
     */
    public int getMinPrecision() {
        switch (this) {
        case DECIMAL:
        case VARCHAR:
        case CHAR:
        case VARBINARY:
        case BINARY:
        case TIME:
        case TIME_WITH_LOCAL_TIME_ZONE:
        case TIMESTAMP:
        case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
            return 1;
        case INTERVAL_YEAR:
        case INTERVAL_YEAR_MONTH:
        case INTERVAL_MONTH:
        case INTERVAL_DAY:
        case INTERVAL_DAY_HOUR:
        case INTERVAL_DAY_MINUTE:
        case INTERVAL_DAY_SECOND:
        case INTERVAL_HOUR:
        case INTERVAL_HOUR_MINUTE:
        case INTERVAL_HOUR_SECOND:
        case INTERVAL_MINUTE:
        case INTERVAL_MINUTE_SECOND:
        case INTERVAL_SECOND:
            return MIN_INTERVAL_START_PRECISION;
        default:
            return -1;
        }
    }

    /**
     * Returns the minimum scale (or fractional second precision in the case of
     * intervals) allowed for this type, or -1 if precision/length are not
     * applicable for this type.
     *
     * @return Minimum allowed scale
     */
    public int getMinScale() {
        switch (this) {
        // TODO: Minimum numeric scale for decimal
        case INTERVAL_YEAR:
        case INTERVAL_YEAR_MONTH:
        case INTERVAL_MONTH:
        case INTERVAL_DAY:
        case INTERVAL_DAY_HOUR:
        case INTERVAL_DAY_MINUTE:
        case INTERVAL_DAY_SECOND:
        case INTERVAL_HOUR:
        case INTERVAL_HOUR_MINUTE:
        case INTERVAL_HOUR_SECOND:
        case INTERVAL_MINUTE:
        case INTERVAL_MINUTE_SECOND:
        case INTERVAL_SECOND:
            return MIN_INTERVAL_FRACTIONAL_SECOND_PRECISION;
        default:
            return -1;
        }
    }

    /**
     * Returns {@code HOUR} for {@code HOUR TO SECOND} and {@code HOUR},
     * {@code SECOND} for {@code SECOND}.
     */
    public TimeUnit getStartUnit() {
        switch (this) {
        case INTERVAL_YEAR:
        case INTERVAL_YEAR_MONTH:
            return TimeUnit.YEAR;
        case INTERVAL_MONTH:
            return TimeUnit.MONTH;
        case INTERVAL_DAY:
        case INTERVAL_DAY_HOUR:
        case INTERVAL_DAY_MINUTE:
        case INTERVAL_DAY_SECOND:
            return TimeUnit.DAY;
        case INTERVAL_HOUR:
        case INTERVAL_HOUR_MINUTE:
        case INTERVAL_HOUR_SECOND:
            return TimeUnit.HOUR;
        case INTERVAL_MINUTE:
        case INTERVAL_MINUTE_SECOND:
            return TimeUnit.MINUTE;
        case INTERVAL_SECOND:
            return TimeUnit.SECOND;
        default:
            throw new AssertionError(this);
        }
    }

    /**
     * Returns {@code SECOND} for both {@code HOUR TO SECOND} and
     * {@code SECOND}.
     */
    public TimeUnit getEndUnit() {
        switch (this) {
        case INTERVAL_YEAR:
            return TimeUnit.YEAR;
        case INTERVAL_YEAR_MONTH:
        case INTERVAL_MONTH:
            return TimeUnit.MONTH;
        case INTERVAL_DAY:
            return TimeUnit.DAY;
        case INTERVAL_DAY_HOUR:
        case INTERVAL_HOUR:
            return TimeUnit.HOUR;
        case INTERVAL_DAY_MINUTE:
        case INTERVAL_HOUR_MINUTE:
        case INTERVAL_MINUTE:
            return TimeUnit.MINUTE;
        case INTERVAL_DAY_SECOND:
        case INTERVAL_HOUR_SECOND:
        case INTERVAL_MINUTE_SECOND:
        case INTERVAL_SECOND:
            return TimeUnit.SECOND;
        default:
            throw new AssertionError(this);
        }
    }

    public boolean isYearMonth() {
        switch (this) {
        case INTERVAL_YEAR:
        case INTERVAL_YEAR_MONTH:
        case INTERVAL_MONTH:
            return true;
        default:
            return false;
        }
    }

    /**
     * Limit.
     */
    public enum Limit {
        ZERO, UNDERFLOW, OVERFLOW
    }

    private BigDecimal getNumericLimit(int radix, int exponent, boolean sign, Limit limit, boolean beyond) {
        switch (limit) {
        case OVERFLOW:

            // 2-based schemes run from -2^(N-1) to 2^(N-1)-1 e.g. -128 to
            // +127
            // 10-based schemas run from -(10^N-1) to 10^N-1 e.g. -99 to +99
            final BigDecimal bigRadix = BigDecimal.valueOf(radix);
            if (radix == 2) {
                --exponent;
            }
            BigDecimal decimal = bigRadix.pow(exponent);
            if (sign || (radix != 2)) {
                decimal = decimal.subtract(BigDecimal.ONE);
            }
            if (beyond) {
                decimal = decimal.add(BigDecimal.ONE);
            }
            if (!sign) {
                decimal = decimal.negate();
            }
            return decimal;
        case UNDERFLOW:
            return beyond ? null : (sign ? BigDecimal.ONE : BigDecimal.ONE.negate());
        case ZERO:
            return BigDecimal.ZERO;
        default:
            throw Util.unexpected(limit);
        }
    }

    public SqlLiteral createLiteral(Object o, SqlParserPos pos) {
        switch (this) {
        case BOOLEAN:
            if (o instanceof Number) {
                return SqlLiteral.createBoolean(((Number) o).intValue() == 1, pos);
            }
            return SqlLiteral.createBoolean((Boolean) o, pos);
        case TINYINT:
        case TINYINT_UNSIGNED:
        case SMALLINT:
        case SMALLINT_UNSIGNED:
        case MEDIUMINT:
        case MEDIUMINT_UNSIGNED:
        case INTEGER:
        case INTEGER_UNSIGNED:
        case BIGINT:
        case BIGINT_UNSIGNED:
        case DECIMAL:
            return SqlLiteral.createExactNumeric(o.toString(), pos);
        case DOUBLE:
            return SqlLiteral.createApproxNumeric(o.toString(), pos);
        case VARCHAR:
        case CHAR:
            if (o instanceof String) {
                return SqlLiteral.createCharString((String) o, pos);
            } else if (o instanceof Slice) {
                return SqlLiteral.createCharString(((Slice) o).toString(CharsetName.DEFAULT_STORAGE_CHARSET_IN_CHUNK), pos);
            }
        case VARBINARY:
        case BINARY:
            return SqlLiteral.createBinaryString((byte[]) o, pos);
        case DATE:
            return SqlLiteral.createDate(DateString.fromObject(o), pos);
        case TIME:
            return SqlLiteral.createTime(TimeString.fromObject(o), 0, pos);
        case TIMESTAMP:
        case DATETIME:
            return SqlLiteral.createTimestamp(TimestampString.fromObject(o), 0, pos);
        default:
            throw Util.unexpected(this);
        }
    }

    /**
     * @return name of this type
     */
    public String getName() {
        return toString();
    }

    /**
     * Flags indicating precision/scale combinations.
     * <p>
     * Note: for intervals:
     * <ul>
     * <li>precision = start (leading field) precision</li>
     * <li>scale = fractional second precision</li>
     * </ul>
     */
    private interface PrecScale {

        int NO_NO = 1;
        int YES_NO = 2;
        int YES_YES = 4;
    }
}

// End SqlTypeName.java
